from paste.httpexceptions import HTTPUnauthorized
from paste.httpheaders import *
try:
    from hashlib import md5
except ImportError:
    from md5 import md5
import time
import random
from urllib import quote as url_quote

def digest_password(realm, username, password):
    return md5(('%s:%s:%s' % (username,
     realm,
     password))).hexdigest()



class AuthDigestAuthenticator(object):

    def __init__(self, realm, authfunc):
        self.nonce = {}
        self.authfunc = authfunc
        self.realm = realm



    def build_authentication(self, stale = ''):
        nonce = md5(('%s:%s' % (time.time(), random.random()))).hexdigest()
        opaque = md5(('%s:%s' % (time.time(), random.random()))).hexdigest()
        self.nonce[nonce] = None
        parts = {'realm': self.realm,
         'qop': 'auth',
         'nonce': nonce,
         'opaque': opaque}
        if stale:
            parts['stale'] = 'true'
        head = ', '.join([ ('%s="%s"' % (k, v)) for (k, v,) in parts.items() ])
        head = [('WWW-Authenticate', ('Digest %s' % head))]
        return HTTPUnauthorized(headers=head)



    def compute(self, ha1, username, response, method, path, nonce, nc, cnonce, qop):
        if not ha1:
            return self.build_authentication()
        else:
            ha2 = md5(('%s:%s' % (method, path))).hexdigest()
            if qop:
                chk = ('%s:%s:%s:%s:%s:%s' % (ha1,
                 nonce,
                 nc,
                 cnonce,
                 qop,
                 ha2))
            else:
                chk = ('%s:%s:%s' % (ha1,
                 nonce,
                 ha2))
            if (response != md5(chk).hexdigest()):
                if (nonce in self.nonce):
                    del self.nonce[nonce]
                return self.build_authentication()
            pnc = self.nonce.get(nonce, '00000000')
            if (nc <= pnc):
                if (nonce in self.nonce):
                    del self.nonce[nonce]
                return self.build_authentication(stale=True)
            self.nonce[nonce] = nc
            return username



    def authenticate(self, environ):
        method = REQUEST_METHOD(environ)
        fullpath = (url_quote(SCRIPT_NAME(environ)) + url_quote(PATH_INFO(environ)))
        authorization = AUTHORIZATION(environ)
        if not authorization:
            return self.build_authentication()
        else:
            (authmeth, auth,) = authorization.split(' ', 1)
            if ('digest' != authmeth.lower()):
                return self.build_authentication()
            amap = {}
            for itm in auth.split(', '):
                (k, v,) = [ s.strip() for s in itm.split('=', 1) ]
                amap[k] = v.replace('"', '')

            try:
                username = amap['username']
                authpath = amap['uri']
                nonce = amap['nonce']
                realm = amap['realm']
                response = amap['response']
                qop = amap.get('qop', '')
                cnonce = amap.get('cnonce', '')
                nc = amap.get('nc', '00000000')
                if qop:
                    pass
            except:
                return self.build_authentication()
            ha1 = self.authfunc(environ, realm, username)
            return self.compute(ha1, username, response, method, authpath, nonce, nc, cnonce, qop)


    __call__ = authenticate


class AuthDigestHandler(object):

    def __init__(self, application, realm, authfunc):
        self.authenticate = AuthDigestAuthenticator(realm, authfunc)
        self.application = application



    def __call__(self, environ, start_response):
        username = REMOTE_USER(environ)
        if not username:
            result = self.authenticate(environ)
            if isinstance(result, str):
                AUTH_TYPE.update(environ, 'digest')
                REMOTE_USER.update(environ, result)
            else:
                return result.wsgi_application(environ, start_response)
        return self.application(environ, start_response)



middleware = AuthDigestHandler
__all__ = ['digest_password', 'AuthDigestHandler']

def make_digest(app, global_conf, realm, authfunc, **kw):
    from paste.util.import_string import eval_import
    import types
    authfunc = eval_import(authfunc)
    return AuthDigestHandler(app, realm, authfunc)


if ('__main__' == __name__):
    import doctest
    doctest.testmod(optionflags=doctest.ELLIPSIS)

